﻿//#define VERBOSE
//#define TEXSAMP
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data;
using RayDen.Library.Data.Imaging;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Renderer;
using RayDen.Library.Entity.Scene;

using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Surface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Samplers
{
    // ray_hit -> full sampled surface data <textures,normals

    [Flags]
    public enum IntersectionOptions : uint
    {
        ResolveObject = 1 << 0,
        ResolveTextures = 1 << 1, // Diffuse & reflectance textures
        ResolveNormalModifiers = 1 << 2, //Bump & Normal map textures
        ResolveSpectralDistributions = 1 << 3,
        ResolvePartialDerivatives = 1 << 4,
        ResolveVolumeProperties = 1 << 5,
        ResumeOnLight = 1 << 6,
        ResolveSurfaceComplete = ResolveObject | ResolveTextures | ResolveNormalModifiers
    }

    public static class IntersectionOptionExtensions
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool Has(this IntersectionOptions val, IntersectionOptions f)
        {
            //return val.HasFlag(f);
            return ((uint)val & (uint)f).Equals((uint)f);
        }



    }


    public class SurfaceSampler : IRadianceSampler
    {
        private QualitySettingsInfo qs;
        private RayEngineScene scene;
        private TextureSamplingManager texSampler;
        private VolumeMaterialSampler vmSampler;
        private VolumeSampler vm;

        internal bool notFillLights;


        public SurfaceSampler(RayEngineScene scene, bool notFillLight = true)
        {
            this.scene = scene;
            this.vm = new VolumeSampler(scene);
            this.vmSampler = new VolumeMaterialSampler();
            this.texSampler = new TextureSamplingManager(scene.TextureSamplingQuality);
            this.notFillLights = notFillLight;
        }

        public BaseBxdf GetBsdf(ref RayData pathRay, ref RayHit hit, ref MediumInfo med, bool fromLight, float u0)
        {
            var currentTriangleIndex = (int) hit.Index;
            bool isLight = scene.IsLight(currentTriangleIndex);

            var mesh = scene.GetMeshByTriangleIndex(currentTriangleIndex);
            if (mesh == null)
            //|| mesh.MeshName.Equals("COL254_01", StringComparison.InvariantCultureIgnoreCase))
            {
                //ii.Color = new RgbSpectrum(1f);
                //Debugger.Break();
                throw new ApplicationException("Invalid triangle index " + currentTriangleIndex + " Mesh not found");
            }
            UV TexCoords;
            Normal normal = new Normal(), shadeN = new Normal();

            mesh.InterpolateTriUV(currentTriangleIndex, hit.U, hit.V, out TexCoords);
            //normal = -scene.Triangles[currentTriangleIndex].ComputeNormal(scene.Vertices).Normalize();

            mesh.InterpolateTriangleNormal((int)hit.Index, hit.U, hit.V, ref normal);
            //normal = -normal;
            shadeN = (Normal.Dot(ref pathRay.Dir, ref normal) > 0f) ? -normal : normal;


            var bsdf =  mesh.Material.GetBsdf(ref pathRay, ref hit, ref normal, ref shadeN, ref TexCoords, ref med, fromLight,u0);
            bsdf.SetLight(isLight);
            return bsdf;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public SurfaceIntersectionData GetIntersection(ref RayData PathRay, ref RayHit hit)
        {
            var ii = new SurfaceIntersectionData(ref hit, false);

            EvalIntersection(ref PathRay, ref hit, ref ii);

            return ii;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public SurfaceIntersectionData GetIntersection(ref RayData PathRay, ref RayHit hit, IntersectionOptions flags)
        {
            var ii = new SurfaceIntersectionData(ref hit, false);

            EvalIntersectionAdvanced(ref PathRay, ref hit, ref ii, flags);

            return ii;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void GetIntersection(ref RayData PathRay, ref RayHit hit, ref SurfaceIntersectionData idata)
        {
            EvalIntersection(ref PathRay, ref hit, ref idata);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void GetIntersection(ref RayData PathRay, ref RayHit hit, ref SurfaceIntersectionData idata, IntersectionOptions flags)
        {
            EvalIntersectionAdvanced(ref PathRay, ref hit, ref idata, flags);
        }

        public void SampleVolume(ref RayHit hit, ref RayData ray, out VolumeHitData vhd)
        {
            var currentTriangleIndex = (int)hit.Index;
            var meshMatID = scene.GetMeshByTriangleIndex(currentTriangleIndex).MaterialID;
            var volumeMaterialInfo = vm.GetMaterialInfo(meshMatID);
            vmSampler.Sample(volumeMaterialInfo, hit.Distance, ref ray, out vhd);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void EvalIntersection(ref RayData PathRay, ref RayHit hit, ref SurfaceIntersectionData ii)
        {
            EvalIntersectionAdvanced(ref PathRay, ref hit, ref ii, IntersectionOptions.ResolveSurfaceComplete);
        }




        private void EvalIntersectionAdvanced(ref RayData PathRay, ref RayHit hit, ref SurfaceIntersectionData ii, IntersectionOptions options)
        {
            ITriangleMesh mesh = null;
#if VERBOSE
            try
            {
#endif



            var currentTriangleIndex = (int)hit.Index;
            bool isLight = scene.IsLight(currentTriangleIndex);
            ii.Hit = hit;
            ii.isLight = isLight;

            mesh = scene.GetMeshByTriangleIndex(currentTriangleIndex);
            if (mesh == null)
            //|| mesh.MeshName.Equals("COL254_01", StringComparison.InvariantCultureIgnoreCase))
            {
                //ii.Color = new RgbSpectrum(1f);
                //Debugger.Break();
                throw new ApplicationException("Invalid triangle index " + currentTriangleIndex + " Mesh not found");
            }
            var meshMat = mesh.MaterialID;

            ii.isVolume = scene.IsVolumeTriangle(currentTriangleIndex);
            ii.MMaterial = scene.MatLib.GetSurfMat(meshMat);
            var matInfo = scene.MaterialProvider.Get(meshMat);

            mesh.InterpolateTriUV(currentTriangleIndex, hit.U, hit.V, out ii.TexCoords);

            if (ii.isVolume)
            {
                var volumeMaterialInfo = vm.GetMaterialInfo(mesh.MaterialID);
                vmSampler.Sample(volumeMaterialInfo, ii.Hit.Distance, ref PathRay, out ii.VolumeData);
                //texSampler.SampleTexture(hit.U, hit.V, volumeMaterialInfo.Density, out ii.VolumeData.Density);
                //this.texSampler.SampleTexture(hit.U, hit.V, new NoiseTexture() ii.VolumeData.Density);
                //return;
            }

            mesh.InterpolateTriangleNormal((int)hit.Index, hit.U, hit.V, ref ii.Normal);

            //ii.Normal = scene.Triangles[currentTriangleIndex].ComputeNormal(scene.Vertices).Normalize();

            //            ii.TexCoords = new UV(hit.U, hit.V);


            if (isLight)
            {
                if (!options.Has(IntersectionOptions.ResumeOnLight))
                    return;
                var color = scene.GetLightByIndex((int)hit.Index).Profile.Evaluate(hit.U, hit.V);
                ii.Color = options.Has(IntersectionOptions.ResolveSpectralDistributions) ? ColorFactory.ToRgb(ref color) : (RgbSpectrum)(RgbSpectrumInfo)color;
            }


            if (options.Has(IntersectionOptions.ResolvePartialDerivatives))
            {
                var p1 = scene.Vertices[scene.Triangles[currentTriangleIndex].v0.VertexIndex];
                var p2 = scene.Vertices[scene.Triangles[currentTriangleIndex].v1.VertexIndex];
                var p3 = scene.Vertices[scene.Triangles[currentTriangleIndex].v2.VertexIndex];

                var mt = scene.sceneData.GeoData.TexCoords;
                var uv1 = mt[scene.sceneData.Triangles[currentTriangleIndex].v0.TexCoordIndex];
                var uv2 = mt[scene.sceneData.Triangles[currentTriangleIndex].v1.TexCoordIndex];
                var uv3 = mt[scene.sceneData.Triangles[currentTriangleIndex].v2.TexCoordIndex];

                float du1 = uv1.x - uv3.x;
                float du2 = uv2.x - uv3.x;
                float dv1 = uv1.y - uv3.y;
                float dv2 = uv2.y - uv3.y;
                Vector dp1 = Vector.Sub(ref p1, ref p3), dp2 = Vector.Sub(ref p2, ref p3);
                float determinant = du1 * dv2 - dv1 * du2;
                if (determinant.NearEqual(0f))
                {
                    // Handle zero determinant for triangle partial derivative matrix
                    Vector e1 = p2 - p1;
                    Vector e2 = p3 - p1;
                    Vector.CoordinateSystem(Vector.Normalize(Vector.Cross(ref e2, ref e1)), out ii.DpDu, out ii.DpDv);
                }
                else
                {
                    float invdet = 1f / determinant;
                    ii.DpDu = (dv2 * dp1 - dv1 * dp2) * invdet;
                    ii.DpDv = (-du2 * dp1 + du1 * dp2) * invdet;
                }

            }

            if (matInfo.Alpha is ConstTextureInfo)
            {
                ii.Alpha = (matInfo.Alpha as ConstTextureInfo).Color;
            }

            if (options.Has(IntersectionOptions.ResolveTextures))
            {

                var diffuseTex = scene.Query(meshMat, TextureType.Diffuse);
                if (diffuseTex != null)
                {

#if !TEXSAMP
                    RgbSpectrumInfo cc = diffuseTex.Sample(ii.TexCoords.U, ii.TexCoords.V);
#else

                    RgbSpectrumInfo cc;
                    texSampler.SampleTexture(ii.TexCoords.U, ii.TexCoords.V, diffuseTex, out cc);
#endif
                    ii.Color = new RgbSpectrum(cc.c1, cc.c2, cc.c3);

                    //ii.Color = diffuseTex.Sample(ii.TexCoords.U, ii.TexCoords.V);
                    //ii.Color = diffuseTex.Sample(hit.U, hit.V);
                }
                else
                {
                    ii.Color = RgbSpectrum.Max(ref matInfo.Ks, ref matInfo.Kd);
                }



                var bump = new Vector(0f);
                Vector v1, v2;
                Normal geoNormal = ii.Normal;
                Vector.CoordinateSystem(ref geoNormal, out v1, out v2);

                var bumpTex = scene.Query(meshMat, TextureType.Bump);
                if (bumpTex != null)
                {
                    var map = bumpTex;
                    var dudv = map.DUDV;
                    RgbSpectrumInfo bs;

#if TEXSAMP

                    texSampler.SampleTexture(ii.TexCoords.U, ii.TexCoords.V, bumpTex, out bs);
#else

                    bs = bumpTex.Sample(ii.TexCoords.U, ii.TexCoords.V);
                    
#endif

                    float b0 = bs.Filter();

                    UV uvdu = new UV(ii.TexCoords.U + dudv.U, ii.TexCoords.V);
#if TEXSAMP
                    texSampler.SampleTexture(uvdu.U, uvdu.V, bumpTex, out bs);
#else
                    bs = bumpTex.Sample(uvdu.U, uvdu.V);
                    
#endif

                    float bu = bs.Filter();

                    UV uvdv = new UV(ii.TexCoords.U, ii.TexCoords.V + dudv.V);
#if TEXSAMP
                    texSampler.SampleTexture(uvdv.U, uvdv.V, bumpTex, out bs);

#else

                    bs = bumpTex.Sample(uvdv.U, uvdv.V);

#endif

                    float bv = bs.Filter();

                    float scale = 1.0f; //bm->GetScale();
                    bump = new Vector(scale * (bu - b0), scale * (bv - b0), 1f);

                    ii.Normal = new Normal(
                        v1.x * bump.x + v2.x * bump.y + geoNormal.x * bump.z,
                        v1.y * bump.x + v2.y * bump.y + geoNormal.y * bump.z,
                        v1.z * bump.x + v2.z * bump.y + geoNormal.z * bump.z).Normalize();
                }

                var nfScale = 2.0f;
                var nfIscale = 0.5f;
                var normTex = scene.Query(meshMat, TextureType.Normal);
                if (normTex != null)
                {
                    RgbSpectrumInfo color;
                    texSampler.SampleTexture(ii.TexCoords.U, ii.TexCoords.V, normTex, out color);
                    float x = nfScale * (color.c1 - nfIscale);
                    float y = nfScale * (color.c2 - nfIscale);
                    float z = nfScale * (color.c3 - nfIscale);

                    ii.Normal = new Normal(
                        v1.x * x + v2.x * y + geoNormal.x * z,
                        v1.y * x + v2.y * y + geoNormal.y * z,
                        v1.z * x + v2.z * y + geoNormal.z * z).Normalize();
                }

                RgbSpectrumInfo reflectance = matInfo.Coefficients[3];
                //(RgbSpectrumInfo) matInfo.SpecularReflectance;
                var reflTex = scene.Query(meshMat, TextureType.Reflection);
                if (reflTex != null)
                {
                    texSampler.SampleTexture(ii.TexCoords.U, ii.TexCoords.V, reflTex, out reflectance);
                }

                RgbSpectrumInfo alpha = null;
                var alphaTex = scene.Query(meshMat, TextureType.Alpha);
                if (alphaTex != null)
                {
                    texSampler.SampleTexture(ii.TexCoords.U, ii.TexCoords.V, alphaTex, out alpha);
                }
                var zero = new RgbSpectrum(0f);

                if (ii.TextureData == null)
                {
                    ii.TextureData = new SurfaceTextureData()
                    {
                        T0 = 0.5f,
                        Transmittance = matInfo.Ks,
                        Exponent = matInfo.Exponent,
                        Alpha = alpha != null ? (RgbSpectrum) alpha : zero,
                        Diffuse = ii.Color,
                        Roughness = bump,
                        Bump = bump,
                        Medium = MediumInfo.Glass,
                        Specular = (RgbSpectrum) reflectance
                    };
                }
                else
                {
                    ii.TextureData.T0 = 0.5f;
                    ii.TextureData.Transmittance = matInfo.Ks;
                    ii.TextureData.Exponent = matInfo.Exponent;
                    ii.TextureData.Alpha = alpha != null ? (RgbSpectrum) alpha : zero;
                    ii.TextureData.Diffuse = ii.Color;
                    ii.TextureData.Roughness = bump;
                    ii.TextureData.Bump = bump;
                    ii.TextureData.Medium = MediumInfo.Glass;
                    ii.TextureData.Specular = (RgbSpectrum) reflectance;
                }
            }
            else
            {
                ii.Color =
                    //matInfo.Kd;
                       RgbSpectrum.Max(ref matInfo.Ks, ref matInfo.Kd);
                ii.TextureData = new SurfaceTextureData()
                {
                    T0 = 0.5f,
                    Transmittance = matInfo.Ks,
                    Exponent = matInfo.Exponent,
                    //Alpha = matInfo.Ka,
                    Diffuse = matInfo.Kd,
                    //Roughness = bump,
                    //Bump = bump,
                    Medium = MediumInfo.Glass,
                    Specular = matInfo.Ks
                };

            }

            /*
            if (mesh.MeshName.Equals("COL254_01", StringComparison.InvariantCultureIgnoreCase))
            {
                //ii.Color = new RgbSpectrum(1f);
                ii.Normal = -scene.Triangles[currentTriangleIndex].ComputeNormal(scene.Vertices).Normalize();
                //ii.ShadingNormal = ii.Normal;
            }
            */

            ii.ShadingNormal = (Normal.Dot(ref PathRay.Dir, ref ii.Normal) > 0f) ? -ii.Normal : ii.Normal;

            if (options.Has(IntersectionOptions.ResolveSpectralDistributions))
            {
                ii.Reflectance = ColorFactory.FromRgb(RgbSpectrum.Max(ref ii.Color, ref matInfo.Kd), SpectrumType.Reflectance);
            }

            //ii.ShadingNormal = ii.Normal;

            /*
            ii.Material = new DistributionBsdf(ii.Color.y(), ii.Color, 
                new FresnelConductor(FresnelApproxEta(ref matInfo.Kd), FresnelApproxK(ref matInfo.Ks))
                //new FresnelDielectric(1.5f, 1f)
                //new FresnelNoOP()
                    );
            new DistributionBsdf(bump.IsZero() ? ii.Color.Filter():bump.Avg(), ii.Color);
             * */
            //new DistributionBsdf(bump.IsZero() ? ii.Color.Filter() : bump.Avg(), ii.Color);


#if VERBOSE

            }
            catch (Exception ex)
            {
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine("Surface sampling error");
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.Message);
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.StackTrace);

                throw;
            }
#endif
        }
        static Dictionary<string, BxDFBase> bsdfs = new Dictionary<string, BxDFBase>();

        private static readonly object addLock = new object();


        static RgbSpectrum FresnelApproxEta(ref RgbSpectrum Fr)
        {
            RgbSpectrum reflectance = Fr.Clamp(0f, .999f);
            return (RgbSpectrum.UnitSpectrum() + RgbSpectrum.Sqrt(ref reflectance)) / (RgbSpectrum.UnitSpectrum() - RgbSpectrum.Sqrt(ref reflectance));
        }

        static RgbSpectrum FresnelApproxK(ref RgbSpectrum Fr)
        {
            RgbSpectrum reflectance = Fr.Clamp(0f, .999f);
            var r = reflectance / (RgbSpectrum.UnitSpectrum() - reflectance);
            return 2f * RgbSpectrum.Sqrt(ref r);
        }

    }


    public sealed class TextureSamplingManager
    {
        public TextureSamplingQuality Quality;
        private readonly ITextureSampler sampler;

        public TextureSamplingManager(TextureSamplingQuality q)
        {
            this.Quality = q;
            if (q == TextureSamplingQuality.Linear)
            {
                this.sampler = new LinearTextureSampler();
            }
            else if (q == TextureSamplingQuality.Nearest)
            {
                this.sampler = new NearestTextureSampler();
            }
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void SampleTexture(float u, float v, ITexture texture, out RgbSpectrumInfo sample)
        {
            if (texture is RgbSpectrumTexture)
            {
                this.sampler.Sample(u, v, texture, out sample);
            }
            else
            {
                sample = texture.Sample(u, v);
            }
        }
    }

  
}
